
package la.foton.atividade.util.exception;

import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.security.web.util.ThrowableCauseExtractor;
import org.springframework.web.filter.GenericFilterBean;

/**
 * Este filtro é utilizado para redirecionar as requisições ajax, quando a sessão do usuário estiver espirada. A configuração da url para
 * redirecionamento(urlToRedirect) é feita no arquivo de configuração do spring security(applicationContext-security.xml)
 * 
 * @author Novembro/2013: Gleyson Carlos dos Santos <DD>
 */
public class AjaxTimeoutRedirectFilter extends GenericFilterBean
{

   private static final Logger logger = LoggerFactory.getLogger(AjaxTimeoutRedirectFilter.class);

   private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
   private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
   private String urlToRedirect;

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
   {
      try
      {
         chain.doFilter(request, response);

         logger.debug("Processamento normal.");
      }
      catch (IOException ex)
      {
         throw ex;
      }
      catch (Exception ex)
      {
         Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
         RuntimeException ase =
            (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
         FacesContext.getCurrentInstance();

         if (ase == null)
         {
            ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
         }

         if (ase != null)
         {
            if (ase instanceof AuthenticationException)
            {
               throw ase;
            }
            else if (ase instanceof AccessDeniedException)
            {

               if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication()))
               {
                  logger.debug("A sessão do usuário expirou.");
                  String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");

                  if ("XMLHttpRequest".equals(ajaxHeader))
                  {
                     String redirectUrlComplete = getUrlToRedirectComplete((HttpServletRequest) request, true, true);
                     logger.debug("Session expired due to ajax request, redirecting to '{}'", redirectUrlComplete);
                     String ajaxRedirectXml = createAjaxRedirectXml(redirectUrlComplete);
                     logger.debug("Ajax partial response to redirect: {}", ajaxRedirectXml);
                     response.setContentType("text/xml");
                     response.getWriter().write(ajaxRedirectXml);
                  }
                  else
                  {
                     logger.info("Redirecionamento para '{}'", getUrlToRedirect());
                     throw ase;
                  }
               }
               else
               {
                  throw ase;
               }
            }
         }

      }
   }

   private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer
   {

      /**
       * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
       */
      protected void initExtractorMap()
      {
         super.initExtractorMap();

         registerExtractor(ServletException.class, new ThrowableCauseExtractor()
         {

            public Throwable extractCause(Throwable throwable)
            {
               ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
               return ((ServletException) throwable).getRootCause();
            }
         });
      }

   }

   /**
    * Monta o cabeçalho da resposta passando a url para redirecionamento
    * 
    * @param redirectUrl
    * @return
    */
   private String createAjaxRedirectXml(String redirectUrl)
   {
      return new StringBuilder()
               .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
               .append("<partial-response><redirect url=\"")
               .append(redirectUrl)
               .append("\"></redirect></partial-response>")
               .toString();
   }

   /**
    * @TODO Monta a url completa para redirecionamento
    * @param error
    * @param message
    * @return
    */
   public String getUrlToRedirectComplete(HttpServletRequest request, Boolean isError, Boolean isMessage)
   {
      StringBuilder urlToRedirectComplete = new StringBuilder(request.getServletContext().getContextPath());
      urlToRedirectComplete.append(getUrlToRedirect());

      if (isError)
      {
         urlToRedirectComplete.append("?error=");
         urlToRedirectComplete.append(String.valueOf(0));
         // request.setAttribute("error", "true");
        
      }
      if (isError && isMessage)
      {
         // urlToRedirectComplete.append("&amp;message=");
         // urlToRedirectComplete.append(PropertiesUtil.MESSAGE_ERROR.getValue(0));
         // request.setAttribute("message", PropertiesUtil.MESSAGE_ERROR.getValue(0));
      }

      return urlToRedirectComplete.toString();
   }

   /**
    * Metodo get do atributo urlToRedirect
    * 
    * @return
    */
   public String getUrlToRedirect()
   {
      return urlToRedirect;
   }

   /**
    * Metodo set do atributo urlToRedirect
    * 
    * @param urlToRedirect
    */
   public void setUrlToRedirect(String urlToRedirect)
   {
      this.urlToRedirect = urlToRedirect;
   }

}
